#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define T_LINE_SPRITE 2
#define T_FACE_HALFTRANS 11
#define T_FACE_BUCKET 13
#define T_FACE_FLAT 15
#define T_FACE_TEX 18
#define T_FACE_OTHER 19
#define T_LINE 20
#define T_POINT_LIGHT 25
#define T_POINT_SMOKE 26

struct s_vertex
{
	int x, y, z;
};

struct s_face
{
	unsigned short v_count;

	unsigned char faceflag[11];

	unsigned short flag0;
	unsigned short is_light;
	unsigned int face_group;
	unsigned char param0;
	unsigned char param1;
	unsigned char param2;

	unsigned short *v;
	short *t_f0;
	short *t_f1;
	short *t_f2;
	short *t_f3;
};

struct s_obj
{
	char name[17];
	char desc[88];
	unsigned int vertex_count;
	unsigned int face_count;
	unsigned int scale;

	struct s_vertex **v;
	struct s_face **f;
};


unsigned int read_int(FILE *fp)
{
	unsigned char buffer[4];
	if (4 != fread(buffer,1,4,fp))
	{
		printf("Error: failed to read 4 bytes from file\n");
		exit(-1);
	}
	return ( buffer[0] |
		(buffer[1] << 8) |
		(buffer[2] << 16) |
		(buffer[3] << 24) );
}

unsigned short read_short(FILE *fp)
{
	unsigned char buffer[2];
	if (2 != fread(buffer,1,2,fp))
	{
		printf("Error: failed to read 2 bytes from file\n");
		exit(-1);
	}
	return ( buffer[0] |
		(buffer[1] << 8) );
}

void read_tag(char *buffer, FILE *fp)
{
	memset(buffer,'\0',5);
	if (4 != fread(buffer,1,4,fp))
	{
		printf("Error: failed to read 4 bytes from file\n");
		exit(-1);
	}
}

int main (int argc, char *argv[])
{
	FILE *fp = NULL, *fpo = NULL;

	unsigned char pal[768];
	char buf[256] = {'\0'};
	unsigned int i,j,k,l;
	unsigned int chunk_count;
	unsigned int obj_count;

	struct s_obj **obj = NULL;

	unsigned short vab_count, fab_count;

	if (argc != 3)
	{
		printf("extract_obj\nGreg Kennedy 2012\npart of the MisCopter project\nhttp://code.google.com/p/miscopter\n\nUsage: %s <packed-obj> <output-folder>\n",argv[0]);
		exit(-1);
	}

	// Step 1: open packed-obj file
	fp = fopen(argv[1],"rb");
	if (!fp)
	{
		printf("Error: could not open %s\n",argv[1]);
		perror("Error reported was");
		exit(-1);
	}
	fseek(fp,0,SEEK_END);
	int calc_filesize = ftell(fp);
	rewind(fp);

	// Read directory chunk
	//      This lists the subchunks in file, and their offsets
	read_tag(buf, fp);
	printf("Begin chunk, expected value DIRC, got '%s'\n",buf);
	unsigned int filesize = read_int(fp);
	printf("Calculated filesize %d, reported filesize %d\n",calc_filesize,filesize);
	chunk_count = read_int(fp);
	printf("\t%d Subchunks follow (expected: 2)\n",chunk_count);
	for (i = 0; i < chunk_count; i ++)
	{
		read_tag(buf, fp);
		j = read_int(fp);
		printf("\t%s data begins at position (%08x)\n",buf,j);
	}

	// Process sub-chunks
	for (i = 0; i < chunk_count; i ++)
	{
		printf("** POS=%08x,", (unsigned int)ftell(fp));
		read_tag(buf, fp);
		printf("chunk %d: %s\n", i, buf);
		j = read_int(fp);
		printf("\t%d bytes of data follow\n",j);

		if (strcmp(buf, "CMAP") == 0)
		{
			j = read_int(fp);
			printf("\t%d sub-subblocks follow (expect: 1)\n",j);
			read_tag(buf, fp);
			printf("\t**SUBchunk: %s (expect: CMAP)\n", buf);
			j = read_int(fp);
			printf("\t(unknown data: %08x)\n",j);
			if (9 != fread(buf,1,9,fp)) { printf("Failed to read magic colors.\n"); exit(1); }
			printf("\tMAGIC COLORS:");
			for (j = 0; j < 9; j ++) { printf (" 0x%02x", (unsigned char)buf[j]); }
			printf("\n");

			j = read_int(fp);
			printf("\tPalette offset begins at %d (expect: %d)\n",j,(unsigned int)ftell(fp));
			if (768 != fread(pal,1,768,fp)) { printf("Failed to read pal.\n"); exit(1); }
		}
		else if (strcmp(buf, "GEOM") == 0)
		{
			j = read_int(fp);
			printf("\t%d sub-subblocks follow\n",j);
			obj_count = read_int(fp);
			printf("** %d OBJECTS IN FILE ** (expect: %d)\n",obj_count, j - 1);
			j = read_int(fp);
			printf("\tName table begins at offset %08x\n",j);
			j = read_int(fp);
			printf("\tMesh data begins at offset %08x\n",j);

			// entry 0 in the Name Table
			printf("----\nPOS=%08x,", (unsigned int)ftell(fp));
			fread(buf, 1, 17, fp);
			printf(" filename: %s,\n",buf);
			printf("\t\tAll mesh data begins at offset %08x\n",read_int(fp));
			printf("\t\tObjects: %d (expect: %d)\n",read_int(fp),obj_count);
			printf("\t\t(%d, expect: 0)\n",read_int(fp));
			printf("\t\t%d TOTAL VERTICES\n",read_int(fp));
			printf("\t\t(%d, expect: 0)\n",read_int(fp));
			printf("\t\t(%d, expect: 0)\n",read_int(fp));
			printf("\t\t%d TOTAL FACES\n",read_int(fp));
			printf("\t\t%d TOTAL UVCOORDS\n",read_int(fp));
			printf("\t\t(%d, expect: 0)\n",read_int(fp));

			// allocate obj structs
			obj = malloc(obj_count * sizeof(struct s_obj *));

			// Name table begins here.
			for (j = 0; j < obj_count; j ++)
			{
				obj[j] = malloc(sizeof(struct s_obj));

				printf("-> POS=%08x,", (unsigned int)ftell(fp));
				fread(obj[j]->name, 1, 17, fp);
				printf(" obj: %03d, name: %s,\n",j, obj[j]->name);
				printf("\t\tMesh data begins at offset %08x\n",read_int(fp));
				printf("\t\t(%d, expect: 1)\n",read_int(fp));
				printf("\t\t(%d, expect: 0)\n",read_int(fp));
				printf("\t\t%d VERTICES\n",read_int(fp));
				printf("\t\t(%d, expect: 0)\n",read_int(fp));
				printf("\t\t(%d, expect: 0)\n",read_int(fp));
				obj[j]->face_count = read_int(fp);
				printf("\t\t%d FACES\n",obj[j]->face_count);
				obj[j]->vertex_count = read_int(fp);
				printf("\t\t%d UNIQUE VERTICES\n",obj[j]->vertex_count);
				printf("\t\t(%d, expect: 0)\n",read_int(fp));
			}

			/* Duplicate GEOM table follows (?) */
			fseek(fp, 9 * 4 * obj_count, SEEK_CUR);

			for (j = 0; j < obj_count; j ++)
			{
				printf("-> POS=%08x,", (unsigned int)ftell(fp));
				read_tag(buf, fp);
				printf("\t**SUBchunk: %s (expect: OBJX)\n", buf);

				k = read_int(fp) + 12; /* BUGFIX */
				printf("\t%d bytes of data follow\n",k);

				vab_count = read_short(fp);
				printf("\t%d vertices in VAB (expect %d)\n",vab_count,obj[j]->vertex_count);
				fab_count = read_short(fp);
				printf("\t%d faces in FAB (expect %d)\n",fab_count,obj[j]->face_count);
				printf("\t\t(%d, expect: 0)\n",read_int(fp));
				obj[j]->scale = read_int(fp);
				printf("\tScale factor: %d\n",obj[j]->scale);
				printf("\t\t(%d, expect: 0)\n",read_int(fp));
				if (88 != fread(obj[j]->desc,1,88,fp)) { printf("Failed to read desc.\n"); exit(1); }
				printf("\tObject descr: %s\n",obj[j]->desc);

				fseek(fp,12,SEEK_CUR);	/* skip junk */

				obj[j]->v = malloc(vab_count * sizeof(struct s_vertex *));
				for (k = 0; k < vab_count; k ++)
				{
					
					obj[j]->v[k] = malloc(sizeof(struct s_vertex));
					obj[j]->v[k]->x = read_int(fp);
					obj[j]->v[k]->y = read_int(fp);
					obj[j]->v[k]->z = read_int(fp);
					printf("\t\tvert%04d: %d, %d, %d\n",k,obj[j]->v[k]->x,obj[j]->v[k]->y,obj[j]->v[k]->z);
				}

				obj[j]->f = malloc(fab_count * sizeof(struct s_face *));

				for (k = 0; k < fab_count; k ++)
				{
					read_tag(buf, fp);
					printf("\t**SUBchunk: %s (expect: FACE)\n", buf);
					printf("\t%d bytes of data follow\n",read_int(fp));

					obj[j]->f[k] = malloc(sizeof(struct s_face));
					obj[j]->f[k]->v_count = read_short(fp);
					printf("\t\tface%04d: %d vert\n",k,obj[j]->f[k]->v_count);

					obj[j]->f[k]->flag0 = read_short(fp);
					obj[j]->f[k]->is_light = read_short(fp);
					obj[j]->f[k]->face_group = read_int(fp);
					fread(&obj[j]->f[k]->param0,1,1,fp);
					fread(&obj[j]->f[k]->param1,1,1,fp);
					fread(&obj[j]->f[k]->param2,1,1,fp);

					obj[j]->f[k]->v = malloc(obj[j]->f[k]->v_count * sizeof(unsigned short));
					for (l = 0; l < obj[j]->f[k]->v_count; l ++)
					{
						obj[j]->f[k]->v[l] = read_short(fp);
					}

					obj[j]->f[k]->t_f0 = malloc(obj[j]->f[k]->v_count * sizeof(short));
					obj[j]->f[k]->t_f1 = malloc(obj[j]->f[k]->v_count * sizeof(short));
					obj[j]->f[k]->t_f2 = malloc(obj[j]->f[k]->v_count * sizeof(short));
					obj[j]->f[k]->t_f3 = malloc(obj[j]->f[k]->v_count * sizeof(short));
					for (l = 0; l < obj[j]->f[k]->v_count; l ++)
					{
						obj[j]->f[k]->t_f0[l] = read_short(fp);
						obj[j]->f[k]->t_f1[l] = read_short(fp);
						obj[j]->f[k]->t_f2[l] = read_short(fp);
						obj[j]->f[k]->t_f3[l] = read_short(fp);
					}
				}

				/* DUMP OBJ FILE */
				sprintf(buf,"%s/%s.obj",argv[2],obj[j]->name);
				fpo = fopen(buf,"w");
				fprintf(fpo,"mtllib %s.mtl\n",obj[j]->name);
				fprintf(fpo,"# NAME: %s\n# DESCRIPTION: %s\n# SCALE: %d\n# VERTICES: %d\n# FACES: %d\n# ORIGIN: %d %d %d\n\n",obj[j]->name,obj[j]->desc,obj[j]->scale,obj[j]->vertex_count,obj[j]->face_count,obj[j]->v[0]->x,obj[j]->v[0]->y,obj[j]->v[0]->z);
				for (k = 1; k < obj[j]->vertex_count; k ++)
				{
					fprintf(fpo,"v %d %d %d\n",obj[j]->v[k]->x,obj[j]->v[k]->y,obj[j]->v[k]->z);
				}
				for (k = 0; k < obj[j]->face_count; k ++)
				{
					fprintf(fpo,"\nusemtl m_f%04d\n",k);
					fprintf(fpo,"# facegroup=%d is_light=%d flag0=%d param0=%d param1 (texnum / color)=%d param2 (tex_file)=%d\n", obj[j]->f[k]->face_group, obj[j]->f[k]->is_light, obj[j]->f[k]->flag0, obj[j]->f[k]->param0, obj[j]->f[k]->param1, obj[j]->f[k]->param2);

					fprintf(fpo,"# next-face flags: ");
					for (l = 0; l < obj[j]->f[k]->v_count; l ++)
					{
						fprintf(fpo," %d/%d/%d/%d",obj[j]->f[k]->t_f0[l],obj[j]->f[k]->t_f1[l],obj[j]->f[k]->t_f2[l],obj[j]->f[k]->t_f3[l]);
					}
					fprintf(fpo,"\n");

					if (obj[j]->f[k]->v_count == 1)
						fprintf(fpo,"p");
					else if (obj[j]->f[k]->v_count == 2)
						fprintf(fpo,"l");
					else
						fprintf(fpo,"f");
					for (l = 0; l < obj[j]->f[k]->v_count; l ++)
					{
						fprintf(fpo," %d",obj[j]->f[k]->v[l]);
					}
					fprintf(fpo,"\n");
				}
				fclose(fpo);

				/* dump mtl file */
				sprintf(buf,"%s/%s.mtl",argv[2],obj[j]->name);
				fpo = fopen(buf,"w");
				for (k = 0; k < obj[j]->face_count; k ++)
				{
					fprintf(fpo,"newmtl m_f%04d\n",k);
					/* switch based on type */
					if (obj[j]->f[k]->param0 == T_LINE || obj[j]->f[k]->param0 == T_POINT_LIGHT || obj[j]->f[k]->param0 == T_FACE_FLAT || obj[j]->f[k]->param0 == T_FACE_OTHER || obj[j]->f[k]->param0 == T_FACE_HALFTRANS || obj[j]->f[k]->param0 == T_FACE_BUCKET || obj[j]->f[k]->param0 == T_POINT_SMOKE)
					{
						unsigned char c = obj[j]->f[k]->param1;
						fprintf(fpo,"Ka %f %f %f\n",pal[3 * c] / 255.0, pal[3 * c + 1] / 255.0, pal[3 * c + 2] / 255.0);
						fprintf(fpo,"Kd %f %f %f\n",pal[3 * c] / 255.0, pal[3 * c + 1] / 255.0, pal[3 * c + 2] / 255.0);
						if (obj[j]->f[k]->param0 == T_FACE_HALFTRANS)
						{
							fprintf(fpo,"d 0.500\n");
						}
					} else if (obj[j]->f[k]->param0 == T_FACE_TEX || obj[j]->f[k]->param0 == T_LINE_SPRITE) {
						fprintf(fpo,"Ka 1.0 1.0 1.0\n");
						fprintf(fpo,"Kd 1.0 1.0 1.0\n");

						fprintf(fpo,"map_Ka sim3d%04d.bmp\n",obj[j]->f[k]->param2);
						fprintf(fpo,"map_Kd sim3d%04d.bmp\n",obj[j]->f[k]->param2);
					} else {
						fprintf(fpo,"Ka 1.0 1.0 1.0\n");
						fprintf(fpo,"Kd 1.0 1.0 1.0\n");

						printf("Unknown param0 %d\n",obj[j]->f[k]->param0);
					}
				}
				fclose(fpo);
			}
			printf("All done with GEOM.\n");
		}
		else
		{
			printf("Unknown tag.  Skipping.\n");
			fseek(fp,j - 8,SEEK_CUR);
		}
	}

	fclose(fp);

	return 0;
}
